using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.RecipeFramework;
using Microsoft.Practices.ComponentModel;
using EnvDTE;
using Microsoft.Practices.RecipeFramework.Library;
using System.Diagnostics;
using System.IO;
using EnvDTE80;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Windows.Forms.Design;
using System.Windows.Forms;
using Microsoft.Practices.RecipeFramework.Extensions.CommonHelpers;
using Resources=Microsoft.Practices.RepositoryFactory.Properties.Resources;

namespace Microsoft.Practices.RepositoryFactory.Actions.VisualStudio
{
    /// <summary/>
    [ServiceDependency(typeof(DTE))]
    [ServiceDependency(typeof(IUIService))]
    public class SplitAndAddItemsFromStringAction : ConfigurableAction
    {
        const string FilenameGroups = "filename";
        const string ContentGroups = "content";
        const string SPLITTER_REGEX = @"^{0} begin ""(?<filename>[^""]+)"".?$\n(?<content>.*)^{0} end ""\k<filename>"".?$";
        private string _content;
        private bool _open = true;
        private string _language = "CS";
        private bool _overwrite = true;
        private bool _showConfirmation = true;
        private Project _project;
        
        #region Input Properties

        private Regex GetRegExSplitter(string language)
        {
            Regex returnRegEx = null;
            string commentChars = @"//";

            switch(language.ToUpper())
            {
                case "VB":
                    commentChars = "--";
                    break;
                case "CS":
                    commentChars = @"//";
                    break;
            }
            returnRegEx = new Regex(String.Format(SPLITTER_REGEX, commentChars), RegexOptions.Multiline | RegexOptions.Singleline);
            return returnRegEx;
        }

     
        /// <summary>
        /// Gets or sets the content.
        /// </summary>
        /// <value>The content.</value>
        [Input(Required = true)]
        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }

        /// <summary>
        /// Gets or sets the project.
        /// </summary>
        /// <value>The project.</value>
        [Input(Required = true)]
        public Project Project
        {
            get { return _project; }
            set { _project = value; }
        }

        [Input(Required = false)]
        public string RecipeLanguage
        {
            get { return _language; }
            set { _language = value; }
        }


        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="T:SplitAndAddItemsFromStringAction"/> is open.
        /// </summary>
        /// <value><c>true</c> if open; otherwise, <c>false</c>.</value>
        [Input]
        public bool Open
        {
            get { return _open; }
            set { _open = value; }
        }



        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="T:SplitAndAddItemsFromStringAction"/> is overwrite.
        /// </summary>
        /// <value><c>true</c> if overwrite; otherwise, <c>false</c>.</value>
        [Input]
        public bool Overwrite
        {
            get { return _overwrite; }
            set { _overwrite = value; }
        }


        /// <summary>
        /// Gets or sets a value indicating whether [show confirmation].
        /// </summary>
        /// <value><c>true</c> if [show confirmation]; otherwise, <c>false</c>.</value>
        [Input]
        public bool ShowConfirmation
        {
            get { return _showConfirmation; }
            set { _showConfirmation = value; }
        }

        #endregion Input Properties

        #region Output Properties

        private ICollection<EnvDTE.ProjectItem> _projectItems;

        /// <summary>
        /// Gets or sets the project item.
        /// </summary>
        /// <value>The project item.</value>
        [Output]
        public ICollection<EnvDTE.ProjectItem> ProjectItem
        {
            get { return _projectItems; }
            set { _projectItems = value; }
        }

        #endregion Output Properties

        /// <summary>
        /// Executes this instance.
        /// </summary>
        public override void Execute()
        {
            DTE vs = GetService<DTE>(true);
            _projectItems = new List<EnvDTE.ProjectItem>();

            Regex splitterRegEx = GetRegExSplitter(_language);

            for (Match match = splitterRegEx.Match(_content); match.Success; match = match.NextMatch())
            {
                string relativeFileName = match.Groups[FilenameGroups].Value;
                string fileName = Path.GetFileName(relativeFileName);
                string relativeTargetPath = Path.GetDirectoryName(relativeFileName);

                ProjectItem projectItem = null;

                string tempfile = Path.GetTempFileName();
                using (StreamWriter sw = new StreamWriter(tempfile, false, Encoding.UTF8))
                {
                    sw.WriteLine(match.Groups[ContentGroups].Value);
                }

                if(_project.Object is SolutionFolder)
                {
                    if(!string.IsNullOrEmpty(relativeTargetPath))
                    {
                        throw new InvalidOperationException(
                            String.Format(CultureInfo.CurrentCulture, 
                            Resources.IntermediateFoldersNotSupportedInSolutionFolders, relativeFileName));
                    }

                    // Copy the file and add it explicitly from the right location.
                    string targetPath = Path.Combine(
                        Path.GetDirectoryName(vs.Solution.FileName),
                        DteHelper.BuildPath(_project));

                    if(!Directory.Exists(targetPath))
                    {
                        Directory.CreateDirectory(targetPath);
                    }

                    string fileFullPath = Path.Combine(targetPath, match.Groups[FilenameGroups].Value);
                    
                    if(_overwrite || !File.Exists(fileFullPath))
                    {
                        File.Copy(tempfile, fileFullPath, true);
                        projectItem = _project.ProjectItems.AddFromFile(fileFullPath);
                    }
                }
                else
                {
                    string fileFullPath = Path.Combine(
                        Path.GetDirectoryName(_project.FileName),
                        relativeFileName);

                    ProjectItems targetProjectItems = GetTargetProjectItems(_project, relativeTargetPath, relativeFileName);
                    ProjectItem existingItem = DteHelperEx.FindItemByName(targetProjectItems, fileName, false);

                    if(existingItem != null)
                    {
                        // the file is already in the project
                        if(_overwrite)
                        {
                            // the item already exists in the project, overwrite it
                            // this is a work around for visual studio: if the file exists and there's an
                            // editor over it, the call to AddFromTemplate will succeed but the contents
                            // of the file will be the ones in the editor, not the ones in the template

                            if(_showConfirmation)
                            {
                                IUIService uiService = GetService<IUIService>();

                                if(uiService.ShowMessage(
                                    string.Format(CultureInfo.CurrentCulture, Resources.ConfirmOverwrite, fileName), 
                                    null, 
                                    MessageBoxButtons.YesNo) == DialogResult.Yes)
                                {
                                    File.Copy(tempfile, fileFullPath, true);
                                }
                            }
                            else
                            {
                                File.Copy(tempfile, fileFullPath, true);
                            }
                        }
                    }
                    else
                    {
                        // the item doesn't exist in the project (but might be in the file system)
                        if(File.Exists(fileFullPath))
                        {
                            // the file exists, but it's not in the project, remove the file
                            File.Delete(fileFullPath);
                        }

                        // add the item to the project, creating the physical file for it
                        projectItem = targetProjectItems.AddFromTemplate(tempfile, fileName);
                    }
                }

                if(projectItem != null)
                {
                    _projectItems.Add(projectItem);
                    if(_open)
                    {
                        Window wnd = projectItem.Open(Constants.vsViewKindPrimary);
                        wnd.Visible = true;
                        wnd.Activate();
                    }
                }
                File.Delete(tempfile);
            }
        }

        private static ProjectItems GetTargetProjectItems(Project project, string relativeTargetPath, string fileName)
        {
            if(string.IsNullOrEmpty(relativeTargetPath))
            {
                return project.ProjectItems;
            }
            else
            {
                //ProjectItem targetprojectFolder
                //    = DteHelper.FindInCollection(project.ProjectItems, relativeTargetPath);
                string[] fragments = relativeTargetPath.Split(new char[] {
					Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

                ProjectItem targetProjectFolder = FindOrCreateProjectFolder(project.ProjectItems, fragments, 0);

                if(targetProjectFolder == null)
                {
                    throw new InvalidOperationException(
                        String.Format(CultureInfo.CurrentCulture, 
                        Resources.MissingTargetFolder, relativeTargetPath, project.Name, fileName));
                }

                if(targetProjectFolder.Kind != Constants.vsProjectItemKindPhysicalFolder)
                {
                    throw new InvalidOperationException(
                        String.Format(CultureInfo.CurrentCulture, 
                        Resources.TargetItemNotAFolder, relativeTargetPath, project.Name));
                }

                return targetProjectFolder.ProjectItems;
            }
        }

        private static ProjectItem FindOrCreateProjectFolder(ProjectItems items, string[] fragments, int index)
        {
            string path = fragments[index];

            foreach(ProjectItem item in items)
            {
                if(string.Compare(item.Name, path, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    if(index == fragments.Length - 1)
                    {
                        return item;
                    }
                    else
                    {
                        return FindOrCreateProjectFolder(item.ProjectItems, fragments, ++index);
                    }
                }
            }

            // Didn't find it, create structure.
            ProjectItems currentItems = items;
            ProjectItem currentItem = null;

            int i = index;
            do
            {
                try
                {
                    currentItem = currentItems.AddFolder(fragments[i], Constants.vsProjectItemKindPhysicalFolder);
                }
                catch(Exception ex)
                {
                    // Retry adding from the physical folder if it exists.
                    string[] currentPath = new string[index + 1];
                    Array.Copy(fragments, currentPath, index + 1);

                    string folderOnDisk = Path.Combine(
                            Path.GetDirectoryName(currentItems.ContainingProject.FileName),
                            String.Join(Path.DirectorySeparatorChar.ToString(), currentPath));

                    if(Directory.Exists(folderOnDisk))
                    {
                        try
                        {
                            currentItem = currentItems.AddFromDirectory(folderOnDisk);

                        }
                        catch(Exception innerEx)
                        {
                            Trace.TraceError(innerEx.ToString());
                            throw;
                        }
                    }
                    else
                    {
                        Trace.TraceError(ex.ToString());
                        throw;
                    }
                }

                currentItems = currentItem.ProjectItems;
                i++;
            } while(i < fragments.Length);

            return currentItem;
        }

        /// <summary>
        /// Undoes the creation of the item, then deletes the item
        /// </summary>
        public override void Undo()
        {
            if(_projectItems != null)
            {
                foreach(ProjectItem projectItem in _projectItems)
                {
                    projectItem.Delete();
                }
            }
        }
    }
}
